﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace JN.Utility
{
    public class DirectedSegment
    {
        private const float TOLERANCE = 0.00001f;
        private PointF _start;
        private PointF _end;
        private double _angle;
        private double _length;


        public PointF Start
        {
            get { return _start; }
        }
        public PointF End
        {
            get { return _end; }
        }

        public double Length
        {
            get { return _length; }
        }
        public double Angle
        {
            get { return _angle; }
        }


        public DirectedSegment(PointF start, PointF end)
            : this(start, end, 0)
        {
        }

        public DirectedSegment(PointF start, PointF end, double width)
        {
            _start = start;
            _end = end;
            float dx = _end.X - _start.X;
            float dy = _end.Y - _start.Y;
            _length = Math.Sqrt(dx * dx + dy * dy);
            _angle = Math.Atan2(dy, dx);

        }

        /// <summary>
        /// 得到平行偏移后的有向线段.
        /// </summary>
        /// <param name="offsetValue">偏移值</param>
        /// <param name="offsetDirection">偏移方向:true 左偏移, false 右偏移</param> 
        /// <returns></returns>
        public DirectedSegment GetOffsetSegment(double offsetValue, bool offsetDirection)
        {
            PointF start = GetOffsetPoint(_start, offsetValue, offsetDirection);
            PointF end = GetOffsetPoint(_end, offsetValue, offsetDirection);
            return new DirectedSegment(start, end);
        }

        /// <summary>
        /// 判断点在不在线上.
        /// </summary>
        /// <returns></returns>
        public static bool IsPointOnSegment(PointF p, DirectedSegment line)
        {
            //bool onLine = (line._end.X - p.X) * (line._end.Y - line._start.Y) == (line._end.X - line._start.X) * (line._end.Y - p.Y);
            bool betweenLineX = p.X > line._start.X && p.X < line._end.X;
            betweenLineX |= p.X < line._start.X && p.X > line._end.X;

            bool betweenLineY = p.Y > line._start.Y && p.Y < line._end.Y;
            betweenLineY |= p.Y < line._start.Y && p.Y > line._end.Y;

            if (betweenLineX && betweenLineY)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 将指定的点向垂直于线段并且远离线段的方向偏移，得到偏移后的点.
        /// </summary>
        /// <param name="offsetValue">偏移值</param>
        /// <param name="offsetDirection">偏移方向:true 左偏移, false 右偏移</param> 
        /// <returns></returns>
        public PointF GetOffsetPoint(PointF p, double offsetValue, bool offsetDirection)
        {
            double angle = _angle + Math.PI / 2; //角度转为正值来计算。
            double dx = offsetValue * Math.Cos(angle);
            double dy = offsetValue * Math.Sin(angle);

            if (!offsetDirection)
            {
                dx = -dx;
                dy = -dy;
            }

            PointF offsetP = new PointF();
            offsetP.X = (float)(p.X + dx);
            offsetP.Y = (float)(p.Y + dy);

            return offsetP;
        }

        /// <summary>
        /// 得到点到线段中线的垂直距离
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public double GetVerticalDistance(PointF p)
        {
            float dx = p.X - _start.X;
            float dy = p.Y - _start.Y;
            double vDistance = 0;

            double angle = Math.Atan2(dy, dx);
            angle = angle - _angle;
            vDistance = Math.Abs(Math.Sqrt(dx * dx + dy * dy) * Math.Sin(angle));
            return vDistance;
        }


        /// <summary>
        /// 判断两条线段是否相交(实交点.) 
        /// </summary>
        /// <param name="line1"></param>
        /// <param name="line2"></param>
        /// <returns></returns>
        public static bool IsLineActualCross(DirectedSegment line1, DirectedSegment line2)
        {
            double line1MinX = Math.Min(line1.Start.X, line1.End.X);
            double line1MaxX = Math.Max(line1.Start.X, line1.End.X);
            double line1MinY = Math.Min(line1.Start.Y, line1.End.Y);
            double line1MaxY = Math.Max(line1.Start.Y, line1.End.Y);

            double line2MinX = Math.Min(line2.Start.X, line2.End.X);
            double line2MaxX = Math.Max(line2.Start.X, line2.End.X);
            double line2MinY = Math.Min(line2.Start.Y, line2.End.Y);
            double line2MaxY = Math.Max(line2.Start.Y, line2.End.Y);

            if (line1MaxX < line2MinX || line1MinX > line2MaxX
                || line1MaxY < line2MinY || line1MinY > line2MaxY)
            {
                return false;
            }

            PointF crossPoint;
            if (GetCrossPoint(line1, line2, out crossPoint) == false)
            {
                return false;
            }

            if (crossPoint.X > line1MaxX
                || crossPoint.X < line1MinX
                || crossPoint.Y > line1MaxY
                || crossPoint.Y < line1MinY
                )
            {
                return false;
            }

            if (crossPoint.X > line2MaxX
               || crossPoint.X < line2MinX
               || crossPoint.Y > line2MaxY
               || crossPoint.Y < line2MinY
               )
            {
                return false;
            }

            double distCrossPointToLine1Start = new DirectedSegment(line1.Start, crossPoint).Length;
            double distCrossPointToLine1End = new DirectedSegment(line1.End, crossPoint).Length;

            double distCrossPointToLine2Start = new DirectedSegment(line2.Start, crossPoint).Length;
            double distCrossPointToLine2End = new DirectedSegment(line2.End, crossPoint).Length;

            if (Math.Abs(distCrossPointToLine1Start + distCrossPointToLine1End - line1.Length) <= TOLERANCE
                && Math.Abs(distCrossPointToLine2Start + distCrossPointToLine2End - line2.Length) <= TOLERANCE)
            {
                return true;
            }

            return false;
        }


        /// <summary>
        /// 得到两条线段中心线的交点.(可能是实交点,也可能是延长线上的虚交点).
        /// </summary>
        /// <param name="line1"></param>
        /// <param name="line2"></param>
        /// <param name="crossPoint"></param>
        /// <returns>若返回false,则表示两条线段平行或重叠. crossPoint为PointF.Empty </returns>
        public static bool GetCrossPoint(DirectedSegment line1, DirectedSegment line2, out PointF crossPoint)
        {
            PointF p1 = line1.Start;
            PointF p2 = line1.End;

            PointF q1 = line2.Start;
            PointF q2 = line2.End;

            crossPoint = PointF.Empty;

            double tempLeft, tempRight;

            //求x坐标 
            tempLeft = (q2.X - q1.X) * (p1.Y - p2.Y) - (p2.X - p1.X) * (q1.Y - q2.Y);
            if (Math.Abs(tempLeft) < TOLERANCE)
            {
                return false;
            }
            tempRight = (p1.Y - q1.Y) * (p2.X - p1.X) * (q2.X - q1.X) + q1.X * (q2.Y - q1.Y) * (p2.X - p1.X) - p1.X * (p2.Y - p1.Y) * (q2.X - q1.X);
            crossPoint.X = (float)(tempRight / tempLeft);

            //求y坐标  
            tempLeft = (p1.X - p2.X) * (q2.Y - q1.Y) - (p2.Y - p1.Y) * (q1.X - q2.X);
            if (Math.Abs(tempLeft) < TOLERANCE)
            {
                return false;
            }
            tempRight = p2.Y * (p1.X - p2.X) * (q2.Y - q1.Y) + (q2.X - p2.X) * (q2.Y - q1.Y) * (p1.Y - p2.Y) - q2.Y * (q1.X - q2.X) * (p2.Y - p1.Y);
            crossPoint.Y = (float)(tempRight / tempLeft);
            return true;
        }


        public static void Test()
        {
            DirectedSegment line1 = new DirectedSegment(new PointF(0, 0), new PointF(100, 100));
            DirectedSegment line2 = new DirectedSegment(new PointF(200, 200), new PointF(300, 300));
            DirectedSegment line3 = new DirectedSegment(new PointF(0, 0), new PointF(100, 100));
            DirectedSegment line4 = new DirectedSegment(new PointF(-70.7107f, 70.7107f), new PointF(29.2893f, 170.7107f));
            DirectedSegment line5 = new DirectedSegment(new PointF(96.9859f, 22.2702f), new PointF(6.6168f, 64.3543f));

            bool isCross = IsLineActualCross(line1, line2);
            PointF p;
            GetCrossPoint(line1, line2, out p);

            isCross = IsLineActualCross(line1, line3);
            GetCrossPoint(line1, line3, out p);

            isCross = IsLineActualCross(line1, line4);
            GetCrossPoint(line1, line4, out p);

            isCross = IsLineActualCross(line1, line5);
            GetCrossPoint(line1, line5, out p);

            isCross = IsLineActualCross(line4, line5);
            GetCrossPoint(line4, line5, out p);

        }



        public static bool IsParallel(DirectedSegment a, DirectedSegment b)
        {
            return (Math.Abs(a.Angle - b.Angle) <= TOLERANCE || Math.Abs((a.Angle - b.Angle) - Math.PI) <= TOLERANCE);
        }

        public static bool IsJoin(DirectedSegment a, DirectedSegment b)
        {
            if (isEqualPoint(a.End, b.Start))
            {
                return true;
            }
            if (isEqualPoint(a.End, b.End))
            {
                return true;
            }
            if (isEqualPoint(a.Start, b.Start))
            {
                return true;
            }
            if (isEqualPoint(a.Start, b.End))
            {
                return true;
            }
            return false;
        }

        private static bool isEqualPoint(PointF a, PointF b)
        {
            float dx = Math.Abs(a.X - b.X);
            float dy = Math.Abs(a.Y - b.Y);
            return (dx <= TOLERANCE && dy <= TOLERANCE);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Angle=");
            sb.Append(Math.Round(_angle / Math.PI * 180, 1));
            sb.Append(",start.X=");
            sb.Append(_start.X);
            sb.Append(",start.Y=");
            sb.Append(_start.Y);
            return sb.ToString();
        }

    }


    public class DirectedWidthSegment : DirectedSegment
    {
        private const float TOLERANCE = 0.00001f;
        private PointF _rightStart;
        private PointF _rightEnd;
        private PointF _leftStart;
        private PointF _leftEnd;
        private double _width;
        private bool _boolFlag;
        private int _intFlag;


        public PointF LeftStart
        {
            get { return _leftStart; }
        }
        public PointF LeftEnd
        {
            get { return _leftEnd; }
        }
        public PointF RightStart
        {
            get { return _rightStart; }
        }
        public PointF RightEnd
        {
            get { return _rightEnd; }
        }
        public double Width
        {
            get { return _width; }
        }
        public bool BoolFlag
        {
            get { return _boolFlag; }
            set { _boolFlag = value; }
        }
        public int IntFlag
        {
            get { return _intFlag; }
            set { _intFlag = value; }
        }

        public DirectedWidthSegment(PointF start, PointF end)
            : this(start, end, 0)
        {
        }

        public DirectedWidthSegment(PointF start, PointF end, double width)
            : base(start, end)
        {
            _width = width;

            double halfWidth = _width / 2;
            _leftStart = GetOffsetPoint(Start, halfWidth, true);
            _leftEnd = GetOffsetPoint(End, halfWidth, true);
            _rightStart = GetOffsetPoint(Start, halfWidth, false);
            _rightEnd = GetOffsetPoint(End, halfWidth, false);
        }

        /// <summary>
        /// 得到平行偏移后的有向线段.
        /// </summary>
        /// <param name="offsetValue">偏移值</param>
        /// <param name="offsetDirection">偏移方向:true 左偏移, false 右偏移</param> 
        /// <returns></returns>
        //public new DirectedWidthSegment GetOffsetSegment(double offsetValue,bool offsetDirection)
        //{
        //    DirectedSegment0 seg = base.GetOffsetSegment(offsetValue, offsetDirection);
        //    return new DirectedWidthSegment(seg.Start, seg.End,_width);  
        //} 


        /// <summary>
        /// 计算两个线段是否是长边重叠.
        /// </summary>
        /// <returns></returns>
        public static bool IsParallelOverlap(DirectedWidthSegment a, DirectedWidthSegment b)
        {
            if (!IsParallel(a, b))
            {
                return false;
            }
            double distance = a.GetVerticalDistance(b.Start);
            if (distance > (a.Width + b.Width) / 2)
            {
                return false;
            }


            float aX1 = a.Start.X;
            float aX2 = a.End.X;
            if (a.Start.X > a.End.X)
            {
                aX1 = a.End.X;
                aX2 = a.Start.X;
            }


            float bX1 = b.Start.X;
            float bX2 = b.End.X;
            if (b.Start.X > b.End.X)
            {
                bX1 = b.End.X;
                bX2 = b.Start.X;
            }


            float aY1 = a.Start.Y;
            float aY2 = a.End.Y;
            if (a.Start.Y > a.End.Y)
            {
                aY1 = a.End.Y;
                aY2 = a.Start.Y;
            }

            float bY1 = b.Start.Y;
            float bY2 = b.End.Y;
            if (b.Start.Y > b.End.Y)
            {
                bY1 = b.End.Y;
                bY2 = b.Start.Y;
            }


            if ((aX2 <= bX1 || aX1 >= bX2) && (aY2 <= bY1 || aY1 >= bY2))
            {
                return false;
            }

            return true;
        }

    }
}
